package keer.httpinterface.proxy;

import keer.httpinterface.HttpInterfaceException;
import keer.httpinterface.annotation.AnnotationConfigure;
import keer.httpinterface.annotation.HttpInterface;
import keer.httpinterface.annotation.Request;
import keer.httpinterface.http.*;
import keer.httpinterface.utils.AnnotationInfo;
import keer.httpinterface.utils.AnnotationUtils;
import keer.httpinterface.utils.CacheMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;

/**
 * 可缓存的{@link ProxyFactory}实现类。使用{@link CacheMap}将创建的代理实例
 * 进行缓存，供后续调用的时候快速获取。
 * @see ProxyFactory
 * @see CacheMap
 */
@SuppressWarnings({"unchecked"})
public abstract class CacheableHttpInterfaceProxyFactory implements ProxyFactory {
    private static final CacheMap<Class<?>,Object> CACHE_MAP = new CacheMap<>();
    private static final Logger logger = LoggerFactory.getLogger(CacheableHttpInterfaceProxyFactory.class);
    protected CacheableHttpInterfaceProxyFactory() {

    }

    @Override
    public <T> T createProxy(Class<T> targetClass, HttpRequester requester) throws HttpInterfaceException {
        Object proxy = CACHE_MAP.loadOrCreate(targetClass, () -> {
            AnnotationInfo httpInterface = AnnotationUtils.getDeclaredAnnotation(targetClass, HttpInterface.class, Boolean.TRUE);
            if (httpInterface == null) {
                return new HttpInterfaceException("Interface must annotated by @HttpInterface:" + targetClass.getName());
            }
            try {
                InvokeCallback callback = new InvokeCallback(requester, targetClass, httpInterface);
                return createInvoker(targetClass, callback);
            } catch (Exception e) {
                return e;
            }
        });
        if (proxy instanceof HttpInterfaceException) {
            throw (HttpInterfaceException) proxy;
        }
        if (proxy instanceof Throwable) {
            throw new HttpInterfaceException("Create proxy instance failure:" + targetClass.getName(), (Throwable) proxy);
        }
        return (T) proxy;
    }

    /**
     * 创建代理实例。由子类实现此方法以创建一个代理实例，子类必须在代理实例的方法执行时，
     * 调用{@link InvokeCallback#doInvoke(Object, Method, Object[])}方法。实现
     * http请求并返回结果
     * @param targetClass 目标接口类型
     * @param callback 回调接口
     * @return 代理实例
     * @throws Exception 创建代理实例失败
     */
    protected abstract <T> T createInvoker(Class<T> targetClass, InvokeCallback callback) throws Exception;

    /**
     * http请求的回调接口。由{@link CacheableHttpInterfaceProxyFactory}创建代理实例时，在代理实例内部方法执行时通过此接口来执行http请求。
     * @see HttpRequester
     * @see AnnotationInfo
     *
     * @author Keer
     * @since 1.0.0
     */
    protected static class InvokeCallback {
        private final Class<?> targetClass;
        private final HttpRequester requester;

        private InvokeCallback(HttpRequester requester,Class<?> targetClass,AnnotationInfo httpInterface) {
            this.requester = requester;
            this.targetClass = targetClass;
        }

        /**
         * 执行http请求
         * @param proxy 目标接口的代理实例
         * @param method 目标方法
         * @param args 方法参数
         * @return 请求结果
         * @throws Throwable 执行http请求时发生异常
         */
        protected Object doInvoke(Object proxy, Method method, Object[] args) throws Throwable {
            AnnotationInfo httpRequest = AnnotationUtils.getMethodAnnotation(method, Request.class, Boolean.TRUE);
            if (httpRequest == null) {
                return null;
            }
            AnnotationConfigure.AnnotationContext context = new AnnotationConfigure.AnnotationContext(targetClass, method, httpRequest, proxy);
            HttpRequest request = HttpRequestAnnotationConfigure.doConfigure(context, args);
            String url = request.getMethod().isHasBody() ? request.getUrl() : appendQueryString(request.getUrl(), request.getParams(), request.getHeaders().charset());
            HttpRequest req = HttpRequest.create(url, request);
            StringBuilder log = new StringBuilder();
            log.append("\n=====================================================\n")
                    .append("[Http Request] ")
                    .append(req.getUrl())
                    .append("\n----------------------------------------------\n")
                    .append(req)
                    .append("\n----------------------------------------------\n");
            HttpResponse response = null;
            try {
                response = requester.request(req);
                log.append(response)
                        .append("\n=====================================================");
                logger.debug("{}", log);
                return method.getReturnType().equals(Void.class) ? null : ResponseConverter.convert(response, method.getReturnType());
            } catch (Exception e) {
                log.append(e.getMessage())
                        .append("\n=====================================================");
                logger.debug("{}", log);
                if (e instanceof HttpInterfaceException) {
                    throw e;
                }
                throw new HttpInterfaceException("Invoke http request failure.", e);
            } finally {
                if (response != null) {
                    response.closeConnection();
                }
            }
        }
    }

    private static String appendQueryString(String url, HttpParams params, String charset) {
        if (url.contains("?")) {
            url += "&";
        } else {
            url += "?";
        }
        url += params.queryString(charset);
        return url;
    }

}